import { arrayUnique, nextTick } from '@/utils';
import { pull, push } from '@/utils/fp';

class Queue {
  values = [];

  callbacks = {};

  canceled = [];

  promise = null;

  constructor(service) {
    this.service = service;
  }

  service = () => Promise.resolve(null);

  enq(value, callback) {
    this.values.push(value);
    const wrappedCb = (val) => {
      callback?.(val);
      this.values = pull(value)(this.values);
    };
    this.callbacks[value] = push(wrappedCb)(this.callbacks[value] ?? []);
    nextTick(this.runOnce);
    return () => {
      this.canceled.push(wrappedCb);
    };
  }

  done(data, values) {
    for (const key of values) {
      const callbacks = this.callbacks[key] ?? [];
      delete this.callbacks[key];
      for (const cb of callbacks) {
        const cancelIdx = this.canceled.indexOf(cb);
        if (cancelIdx > -1) {
          this.canceled.splice(cancelIdx, 1);
          continue;
        }
        cb(data?.[key] ?? null);
      }
    }
  }

  runOnce = () => {
    if (!this.promise) {
      const values = arrayUnique(this.values.splice(0, this.values.length));
      if (!values.length) return;
      this.promise = this.service(values)
        .then((data) => {
          this.done(data, values);
        })
        .finally(() => {
          this.promise = null;
          this.runOnce();
        });
    }
  };
}

export default Queue;
